#include <iostream>
#include <cassert>//C语言的头文件，C++重新打包编译之后，在名字前面加上c
#include <cstring>
#include <unistd.h>//系统头文件，不可以加c
#include <sys/types.h>
#include <sys/wait.h>

#define MAX 1024

using namespace std;

//注释：ctrl+“/”

// a. 管道的4种情况
//    1. 正常情况，如果管道没有数据了，读端必须等待，直到有数据为止(写端写入数据了)
//    2. 正常情况，如果管道被写满了，写端必须等待，直到有空间为止(读端读走数据)
//    3. 写端关闭，读端一直读取, 读端会读到read返回值为0， 表示读到文件结尾
//    4. 读端关闭，写端一直写入，OS会直接杀掉写端进程，通过向目标进程发送SIGPIPE(13)信号，终止目标进程 ---- 如何证明？？
// b. 管道的5种特性
//    1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信，常用与父子,应用场景仅限于此，无血缘关系的进程之间用不了
//    2. 匿名管道，默认给读写端要提供同步机制（也就是写一些再读一些，循环往复，写的时候读就得等待，反之也是） --- 了解现象就行
//    3. 面向字节流的（写虽然是一次一次写的，但是读取可能是一大堆一起读，而不是读取每次写的量） --- 了解现象就行
//    4. 管道的生命周期是随进程的
//    5. 管道是单向通信的，半双工通信（比如双方说话，一方说话时另一方必须倾听）的一种特殊情况（全双工就是类似于一起说）

int main()
{
    // 第1步，建立管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n == 0);//debug模式下才会编译，release模式相当于直接被注释掉了，也就是不被运行
    (void)n; // 在release模式下assert被忽略，也就是可能会警告说n没被使用，加这句为了防止编译器告警
    //自己已经有预定的结果用assert，不能确定的结果用if
    cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;
    //前者是读，后者是写

    // 第2步，创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        return 1;
    }
    // 想要实现：子写，父读
    // 第3步，父子关闭不需要的fd，形成单向通信的管道
    if (id == 0)
    {
        // if(fork() > 0) exit(0); //匿名管道,可以允许具有血缘关系的进程之间进行进程间通信
        // child
        close(pipefd[0]);//因为父子进程一开始都各自可以读写，关掉不需要的
        // w - 只向管道写入，没有打印
        int cnt = 0;
        while(true)
        {
            // char c = 'a';
            // write(pipefd[1], &c, 1);
            // cnt++;
            // cout << "write ....: " << cnt << endl; // 测试出来机器的pipe空间大小是64KB（和版本等有关系）
            
            char message[MAX];
            snprintf(message, sizeof(message), "hello father, I am a child, pid: %d, cnt: %d", getpid(), cnt);//将最后一个参数写入message的缓冲区
            cnt++;
            write(pipefd[1], message, strlen(message));//管道也是文件，所以最后的长度不需要加1
            sleep(1);

            // if(cnt > 3) break;
        }
        cout << "child close w point" << endl;
        // close(pipefd[1]);//注释了这句代码，可以看出进程退出，文件描述符也会被关闭
        exit(0);
    }
    // 父进程
    close(pipefd[1]);

    // r
    char buffer[MAX];
    while(true)
    {
        // sleep(2000);
        ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);//返回实际读取的字节数，-1是防止把缓冲区写满了，最后一个位置留给“\0”
        if(n > 0)
        {
            buffer[n] = 0; // '\0', 当做字符串
            cout << getpid() << ", " << "child say: " << buffer << " to me!" << endl;
        }
        else if(n == 0)//不阻塞且返回值为0，说明写端关闭了
        {
            cout << "child quit, me too !" << endl;
            break;
        }
        cout << "father return val(n): " << n << endl;
        sleep(1);

        break;
    }
    cout << "read point close"<< endl;
    close(pipefd[0]);

    sleep(5);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid == id)
    {
        cout << "wait success, child exit sig: " << (status&0x7F) << endl;
    }
    return 0;
}
